﻿using System;
using System.Xml;
using Notoric.Model.Internal;
using Notoric.Model.Packaging;
using Notoric.Model.Validation;

namespace Notoric.Model.Elements.CWNotation
{
    /// <summary>
    /// A common base class for staff connectors.
    /// </summary>
    public abstract class StaffConnector : ModelElement
    {
        #region Constructors

        /// <summary>
        /// Creates a new <see cref="StaffConnector"/> instance.
        /// </summary>
        /// <param name="packagePart">
        /// <para>
        /// Type: <see cref="XmlPackagePart"/>
        /// </para>
        /// <para>
        /// The package part that owns this element.
        /// </para>
        /// </param>
        /// <param name="owner">
        /// <para>
        /// Type: <see cref="System"/>
        /// </para>
        /// <para>
        /// The model element that directly owns this element.
        /// </para>
        /// </param>
        /// <param name="lowerStaff">
        /// <para>
        /// Type: <see cref="Staff"/>
        /// </para>
        /// <para>
        /// The lower connected staff.
        /// </para>
        /// </param>
        /// <param name="upperStaff">
        /// <para>
        /// Type: <see cref="Staff"/>
        /// </para>
        /// <para>
        /// The upper connected staff.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// Any of the arguments is <see langword="null"/>.
        /// </exception>
        /// <exception cref="ArgumentException">
        /// The <paramref name="lowerStaff"/> is positioned above the <paramref name="upperStaff"/>.<br/>
        /// -or-<br/>
        /// The <paramref name="lowerStaff"/> or the <paramref name="upperStaff"/> does not belong to the <paramref name="owner"/> system.
        /// </exception>
        protected StaffConnector(XmlPackagePart packagePart, System owner, Staff lowerStaff, Staff upperStaff)
            : base(packagePart, owner)
        {
            if (owner == null)
                throw new ArgumentNullException("owner");
            if (lowerStaff == null)
                throw new ArgumentNullException("lowerStaff");
            if (upperStaff == null)
                throw new ArgumentNullException("upperStaff");
            if (lowerStaff.FindOwner<System>() != owner || upperStaff.FindOwner<System>() != owner)
                throw new ArgumentException("Both staff have to be owned by the owner system.");
            if (lowerStaff.Top < upperStaff.Top)
                throw new ArgumentException("The lower staff must be bellowe the upper staff.");

            this.lowerStaff = lowerStaff;
            this.upperStaff = upperStaff;
            this.lowerStaffId.Value = lowerStaff.ID.ToString();
            this.upperStaffId.Value = upperStaff.ID.ToString();
            this.typeAttribute.Value = GetType().FullName;
        }

        /// <summary>
        /// Creates a new <see cref="StaffConnector"/> instance.
        /// </summary>
        /// <param name="packagePart">
        /// <para>
        /// Type: <see cref="XmlPackagePart"/>
        /// </para>
        /// <para>
        /// The package part that owns this element.
        /// </para>
        /// </param>
        /// <param name="owner">
        /// <para>
        /// Type: <see cref="System"/>
        /// </para>
        /// <para>
        /// The model element that directly owns this element.
        /// </para>
        /// </param>
        /// <param name="element">
        /// <para>
        /// Type: <see cref="XmlElement"/>
        /// </para>
        /// <para>
        /// The XML element that holds the data of this object.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// Any of the arguments is <see langword="null"/>.
        /// </exception>
        protected StaffConnector(XmlPackagePart packagePart, System owner, XmlElement element)
            : base(packagePart, owner, element)
        {
            if (owner == null)
                throw new ArgumentNullException("owner");

            lowerStaffId = element.Attributes[PackageVocabulary.LowerStaffAttribute];
            upperStaffId = element.Attributes[PackageVocabulary.UpperStaffAttribute];
            typeAttribute = element.Attributes[PackageVocabulary.TypeAttribute];

            lowerStaff = packagePart.Package.IDMap.FindObject<Staff>(new Guid(lowerStaffId.Value));
            upperStaff = packagePart.Package.IDMap.FindObject<Staff>(new Guid(upperStaffId.Value));
        }

        #endregion

        #region ModelElement Members

        /// <inheritdoc/>
        protected override XmlElement CreateObjectElement(XmlDocument document)
        {
            var element = CreateObjectElementCore(document);

            lowerStaffId = document.CreateAttribute(PackageVocabulary.LowerStaffAttribute);
            upperStaffId = document.CreateAttribute(PackageVocabulary.UpperStaffAttribute);
            typeAttribute = document.CreateAttribute(PackageVocabulary.TypeAttribute);

            element.Attributes.Append(lowerStaffId);
            element.Attributes.Append(upperStaffId);
            element.Attributes.Append(typeAttribute);

            return element;
        }

        #endregion

        #region Protected Methods

        /// <summary>
        /// When overriden in a derived class, this method creates an XML element
        /// that will hold the serialized data of the connector.
        /// </summary>
        /// <remarks>
        /// The attributes for inherited properties as well as the type information
        /// will be added by the base class.
        /// </remarks>
        /// <param name="document">
        /// <para>
        /// Type: <see cref="XmlDocument"/>
        /// </para>
        /// <para>
        /// The XML document that will contain the created element.
        /// </para>
        /// </param>
        /// <returns>
        /// <para>
        /// Type: <see cref="XmlElement"/>
        /// </para>
        /// <para>
        /// The created XML element.
        /// </para>
        /// </returns>
        protected abstract XmlElement CreateObjectElementCore(XmlDocument document);

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets the lower connected staff.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Staff"/>
        /// </para>
        /// <para>
        /// The lower staff of the connector.
        /// </para>
        /// </value>
        public Staff LowerStaff
        {
            get { return lowerStaff; }
        }

        /// <summary>
        /// Gets the upper connected staff.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Staff"/>
        /// </para>
        /// <para>
        /// The upper staff of the connector.
        /// </para>
        /// </value>
        public Staff UpperStaff
        {
            get { return upperStaff; }
        }

        #endregion

        #region Private Fields

        /// <summary>
        /// Holds the value of the <see cref="LowerStaff"/> property.
        /// </summary>
        private Staff lowerStaff;
        /// <summary>
        /// Holds the XML attribute that holds the value of the <see cref="LowerStaff"/> property.
        /// </summary>
        private XmlAttribute lowerStaffId;
        /// <summary>
        /// Holds the value of the <see cref="UpperStaff"/> property.
        /// </summary>
        private Staff upperStaff;
        /// <summary>
        /// Holds the XML attribute that holds the value of the <see cref="UpperStaff"/> property.
        /// </summary>
        private XmlAttribute upperStaffId;
        /// <summary>
        /// Holds the XML attribute that holds the type of the connector class.
        /// </summary>
        private XmlAttribute typeAttribute;

        #endregion
    }
}
